import datetime

from django.db import transaction
from django_redis import get_redis_connection
from redis.exceptions import LockNotOwnedError, LockError
from rest_framework.decorators import action
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response

from social.call.models import AVCall, AVChatTickTime
from social.call.serializers import AVCallSerializer
from social.utils.views import BaseGenericViewSet


class CallViewSet(BaseGenericViewSet, CreateModelMixin, RetrieveModelMixin):
    queryset = AVCall.objects.all()
    serializer_class = AVCallSerializer
    permission_classes = (IsAuthenticated,)

    @action(methods=['POST'], detail=False, permission_classes=(AllowAny,))
    def call_invite(self, request, *args, **kwargs):
        return Response(status=200)

    @action(methods=['POST'], detail=True)
    def accept(self, request, *args, **kwargs):
        call = self.get_object()
        call_id = request.data.get('call_id')
        r = get_redis_connection()
        try:
            with r.lock(f'av_chat:{call.id}', timeout=30, blocking_timeout=30):
                if call.status == AVCall.WAITING:
                    call.status = AVCall.ING
                    call.call_id = call_id
                    call.create_time = datetime.datetime.now().timestamp()
                    call.save(update_fields=['status', 'create_time', 'call_id'])
        except LockNotOwnedError:
            pass
        except LockError:
            pass
        return Response(data=AVCallSerializer(call).data)

    @action(methods=['POST'], detail=True)
    def tick(self, request, *args, **kwargs):
        call = self.get_object()
        if call.status != AVCall.ING:
            return Response(data=AVCallSerializer(call).data)
        r = get_redis_connection()
        try:
            with r.lock(f'av_chat:{call.id}', timeout=30, blocking_timeout=30):
                with transaction.atomic():
                    if call.is_running():
                        duration = datetime.datetime.now().timestamp() - call.create_time
                        total_min = int(duration / 60) + 1
                        total_coin = int(call.coin * total_min)
                        coin = total_coin - call.total_coin
                        if coin > 0:
                            call.update_wallet_record(coin, total_min, total_coin)
                        tick_time = AVChatTickTime.objects.get(chat_id=call.id, user_id=request.user.id)
                        tick_time.save()
        except LockNotOwnedError:
            pass
        except LockError:
            pass
        call.refresh_from_db()
        return Response(data=AVCallSerializer(call).data)

    @action(methods=['POST'], detail=True)
    def hangup(self, request, *args, **kwargs):
        r = get_redis_connection()
        call = self.get_object()
        try:
            with r.lock(f'av_chat:{call.id}', timeout=30, blocking_timeout=30):
                if call.status == AVCall.WAITING:
                    # 还没开始
                    if request.user.id == call.from_user.id:
                        call.status = AVCall.CANCEL
                    else:
                        call.status = AVCall.REJECT
                    call.save(update_fields=['status'])
                elif call.status == AVCall.ING:
                    call.status = AVCall.OVER
                    call.destroy_time = datetime.datetime.now().timestamp()
                    call.save(update_fields=['status', 'destroy_time'])
        except LockNotOwnedError:
            pass
        except LockError:
            pass
        return Response(data=AVCallSerializer(call).data)
